using System;
using System.Collections.Generic;
using Picnic.Spec.Console.ExceptionWriters;

namespace Picnic.Spec.Console
{
	public class ConsoleListener : ISpecificationValidationListener
	{
		private const ConsoleColor ScopeColor = ConsoleColor.White;
		private const ConsoleColor SatisfiedStatementColor = ConsoleColor.Green;
		private const ConsoleColor FailStatementColor = ConsoleColor.Red;
		private const ConsoleColor PassedStatementColor = ConsoleColor.Yellow;

		private readonly SystemConsole _console = new SystemConsole();
		private readonly List<IConsoleExceptionWriter> _exceptionWriters = new List<IConsoleExceptionWriter>();
		private readonly List<Tuple<int, StatementScope, Statement, StatementValidationResult>> _failrules
			= new List<Tuple<int, StatementScope, Statement, StatementValidationResult>>();

		private StatementScope _currentScope;

		/// <summary>
		/// Initializes a new instance of ConsoleListener class.
		/// </summary>
		public ConsoleListener(IEnumerable<IConsoleExceptionWriter> exceptionWriters = null)
		{
			if (exceptionWriters != null)
			{
				_exceptionWriters.AddRange(exceptionWriters);
			}
			_exceptionWriters.Add(new EqualsExceptionWriter());
			_exceptionWriters.Add(new RequiredExceptionIsNotThrownWriter());
			_exceptionWriters.Add(new DefaultExceptionWriter());
		}

		/// <summary>
		/// Called when validation is begin.
		/// </summary>
		public void BeginValidation()
		{
			_failrules.Clear();
		}

		/// <summary>
		/// Called when validation is finished.
		/// </summary>
		public void EndValidation(StatementScopeValidationResult result)
		{
			WriteFailedStatements();
			WriteElapsedTime(result);
			WriteProcessedStatementDetails(result);
		}

		private void WriteProcessedStatementDetails(StatementScopeValidationResult result)
		{
			var totalCount = result.SatisfiedStatementCount + result.PassedStatementCount + result.FailedStatementCount;
			var details = string.Format(
				"{0} statements, {1} passed, {2} failed",
				totalCount,
				result.PassedStatementCount,
				result.FailedStatementCount);

			_console.SetColor(_failrules.Count > 0 ? FailStatementColor : SatisfiedStatementColor);
			_console.WriteLine(details);
			_console.UndoColor();
		}

		private void WriteElapsedTime(StatementScopeValidationResult result)
		{
			_console.SetColor(ScopeColor);
			_console.WriteLine(string.Format("Finished in {0} seconds", result.Elapsed.TotalSeconds));
			_console.UndoColor();
		}

		private void WriteFailedStatements()
		{
			if (_failrules.Count > 0)
			{
				_console.SetColor(ScopeColor);
				_console.WriteLine("Failrules:");
				_console.UndoColor();

				foreach (var failrule in _failrules)
				{
					WriteFailedStatement(failrule);
					_console.WriteLine();
				}
			}
		}

		private void WriteFailedStatement(Tuple<int, StatementScope, Statement, StatementValidationResult> failrule)
		{
			var number = failrule.Item1;
			var scope = failrule.Item2;
			var statement = failrule.Item3;
			var statementResult = failrule.Item4;
			var exceptionPadding = number + ") ".Length;
			var statementText = string.IsNullOrEmpty(scope.Context)
				? number + ") " + scope.Subject + " " + statement.Text
				: number + ") " + scope.Subject + " " + scope.Context + " " + statement.Text;

			_console.SetColor(ScopeColor);
			_console.WriteLine(statementText);
			_console.UndoColor();

			WriteException(statementResult.Error, exceptionPadding);
		}

		/// <summary>
		/// Called when statement scope validation is begin.
		/// </summary>
		public void BeginStatementScopeValidation(StatementScope statementScope)
		{
			_currentScope = statementScope;
			_console.SetColor(ScopeColor);
			if (string.IsNullOrEmpty(statementScope.Context))
			{
				_console.WriteLine(statementScope.Subject);
				return;
			}
			_console.WriteLine(statementScope.Subject + " " + statementScope.Context);
			_console.UndoColor();
		}

		/// <summary>
		/// Called when statement scope validation is finished.
		/// </summary>
		public void EndStatementScopeValidation(StatementScope statementScope, StatementScopeValidationResult result)
		{
			_currentScope = null;
			_console.WriteLine();
		}

		/// <summary>
		/// Called when statement validation is begin.
		/// </summary>
		public void BeginStatementValidation(Statement statement)
		{
		}

		/// <summary>
		/// Called when statement validation is finished.
		/// </summary>
		public void EndStatementValidation(Statement statement, StatementValidationResult result)
		{
			var statementColor = GetStatementColor(result);
			var text = "- " + statement.Text;
			if (result.Kind == StatementValidationResultKind.Failed)
			{
				var failNumber = _failrules.Count + 1;
				text += " (FAILED - " + failNumber + ")";
				AddFail(statement, result);
			}
			_console.SetColor(statementColor);
			_console.WriteLine(text);
			_console.UndoColor();
		}

		private void AddFail(Statement statement, StatementValidationResult result)
		{
			var failNumber = _failrules.Count + 1;
			var fail = new Tuple<int, StatementScope, Statement, StatementValidationResult>(
				failNumber, _currentScope, statement, result);

			_failrules.Add(fail);
		}

		private static ConsoleColor GetStatementColor(StatementValidationResult validationResult)
		{
			switch (validationResult.Kind)
			{
				case StatementValidationResultKind.Failed:
					return FailStatementColor;
				case StatementValidationResultKind.Passed:
					return PassedStatementColor;
			}
			return SatisfiedStatementColor;
		}

		private void WriteException(Exception exception, int padding)
		{
			foreach (var exceptionWriter in _exceptionWriters)
			{
				if (exceptionWriter.CanWrite(exception))
				{
					var console = new SystemConsole(padding);
					exceptionWriter.Write(console, exception);
					return;
				}
			}
		}
	}
}